package com.travel.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * 网络请求工具
 *
 * @author SAGITTAR
 */
public final class HttpClientUtil {

	private static final Logger LOGGER = LogManager.getLogger(HttpClientUtil.class);

	private static CookieStore cookieStore;
	private static CloseableHttpClient httpClient;
	private static RequestConfig requestConfig;

	static {
		try {
			KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
			SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(keyStore, (chain, authType) -> true).build();
			SSLConnectionSocketFactory sslSFactory = new SSLConnectionSocketFactory(sslContext);
			Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
					.register("http", PlainConnectionSocketFactory.INSTANCE).register("https", sslSFactory).build();

			PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);

			SocketConfig.Builder builder = SocketConfig.custom();
			manager.setDefaultSocketConfig(builder.setSoTimeout(Constants.TIMEOUT).setTcpNoDelay(true).build());
			CodingErrorAction ignore = CodingErrorAction.IGNORE;
			ConnectionConfig connectionConfig = ConnectionConfig.custom().setCharset(Consts.UTF_8)
					.setMalformedInputAction(ignore).setUnmappableInputAction(ignore).build();
			manager.setDefaultConnectionConfig(connectionConfig);
			manager.setMaxTotal(500);
			manager.setDefaultMaxPerRoute(300);
			HttpClientBuilder httpClientBuilder = HttpClients.custom().setConnectionManager(manager)
					.setDefaultCookieStore(new BasicCookieStore()).setUserAgent(Constants.USER_AGENT_ARRAY[0])
					.setRetryHandler((exception, executionCount, context) -> {
						if (executionCount > 2) {
							return false;
						}
						if (exception instanceof InterruptedIOException) {
							return true;
						}
						if (exception instanceof UnknownHostException) {
							return true;
						}
						if (exception instanceof SSLException) {
							return true;
						}
						HttpRequest request = HttpClientContext.adapt(context).getRequest();
						return !(request instanceof HttpEntityEnclosingRequest);
					});
			httpClient = httpClientBuilder.build();
			requestConfig = RequestConfig.custom().setSocketTimeout(Constants.TIMEOUT)
					.setConnectTimeout(Constants.TIMEOUT).setConnectionRequestTimeout(Constants.TIMEOUT)
					.setCookieSpec(CookieSpecs.STANDARD).build();
		} catch (Exception e) {
			LOGGER.error(e.getLocalizedMessage(), e);
		}
	}

	private HttpClientUtil() {
	}

	/**
	 * 获取网页源代码，默认编码UTF-8
	 * 
	 * @param url 请求地址
	 * @return
	 * @throws IOException
	 */
	public static String getWebPage(String url) throws IOException {
		return getWebPage(new HttpGet(url), Constants.UTF_8);
	}

	/**
	 * 以指定的编码获取网页源代码
	 * 
	 * @param url 请求地址
	 * @param encoding 编码格式
	 * @return
	 * @throws IOException
	 */
	public static String getWebPage(String url, String encoding) throws IOException {
		return getWebPage(new HttpGet(url), encoding);
	}

	/**
	 * 返回网页内容，默认编码UTF-8
	 * 
	 * @param request 请求对象
	 * @return
	 * @throws IOException
	 */
	public static String getWebPage(HttpRequestBase request) throws IOException {
		return getWebPage(request, Constants.UTF_8);
	}

	/**
	 * 返回网页内容
	 * 
	 * @param request 请求基类
	 * @param encoding 编码格式
	 * @return
	 * @throws IOException
	 */
	public static String getWebPage(HttpRequestBase request, String encoding) throws IOException {
		CloseableHttpResponse response = getResponse(request);
		String content = EntityUtils.toString(response.getEntity(), encoding);
		request.releaseConnection();
		return content;
	}

	/**
	 * HttpPost请求
	 * 
	 * @param postUrl 请求地址
	 * @param params 请求体参数
	 * @return 网页源代码
	 * @throws IOException
	 */
	public static String postRequest(String postUrl, Map<String, String> params) throws IOException {
		List<NameValuePair> formParams = new ArrayList<NameValuePair>();
		for (Map.Entry<String, String> entry : params.entrySet()) {
			formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
		}
		HttpPost httpPost = new HttpPost(postUrl);
		httpPost.setEntity(new UrlEncodedFormEntity(formParams, Constants.UTF_8));
		return getWebPage(httpPost, Constants.UTF_8);
	}

	public static CloseableHttpResponse getResponse(HttpRequestBase request) throws IOException {
		if (Objects.isNull(request.getConfig())) {
			request.setConfig(requestConfig);
		}
		int randomIndex = new Random().nextInt(Constants.USER_AGENT_ARRAY.length);
		request.setHeader("User-Agent", Constants.USER_AGENT_ARRAY[randomIndex]);
		CloseableHttpResponse response = httpClient.execute(request, HttpClientContext.create());
		int statusCode = response.getStatusLine().getStatusCode();
		if (statusCode != 200) {
			throw new IOException("status code is: " + statusCode);
		}
		return response;
	}

	public static CloseableHttpResponse getResponse(String url) throws IOException {
		return getResponse(new HttpGet(url));
	}

	/**
	 * 优化URL
	 * 
	 * @param url 原始URL
	 * @return
	 */
	public static String validUrl(String url) {
		if (StringUtils.startsWith(url, "//")) {
			url = StringUtils.substring(url, 2);
		}
		if (!StringUtils.startsWith(url, "http://")) {
			url = StringUtils.join("http://", url);
		}
		return url;
	}

	/**
	 * 下载图片
	 *
	 * @param fileURL 文件地址
	 * @param path 保存路径
	 * @param saveFileName 文件名
	 * @param isReplaceFile 若存在文件时，是否还需要下载文件
	 */
	public static void downloadFile(String fileURL, String path, String saveFileName, Boolean isReplaceFile) {
		File directory = new File(path);
		// 如果文件夹不存在则创建
		if (!directory.exists() && !directory.isDirectory()) {
			directory.mkdirs();
		}
		if (!StringUtils.contains(saveFileName, ".")) {
			String extention = StringUtils.substring(fileURL, StringUtils.lastIndexOf(fileURL, "."));
			saveFileName = StringUtils.join(saveFileName, extention);
		}
		File destinatedFile = new File(Paths.get(path).resolve(saveFileName).toUri());
		if (!destinatedFile.exists() || isReplaceFile) {
			// 如果文件不存在或需要替换，则下载
			InputStream inputStream = null;
			OutputStream outputStream = null;
			CloseableHttpResponse response = null;
			try {
				response = getResponse(fileURL);
				outputStream = new FileOutputStream(destinatedFile);
				inputStream = response.getEntity().getContent();
				byte[] temp = new byte[10 * 1024];
				int currentRead;
				while ((currentRead = inputStream.read(temp)) != -1) {
					outputStream.write(temp, 0, currentRead);
				}
				LOGGER.info(fileURL + " -> 文件成功下载至 -> " + destinatedFile.getCanonicalPath());
			} catch (IOException | UnsupportedOperationException e) {
				LOGGER.error(e.getLocalizedMessage(), e);
			} finally {
				if (Objects.nonNull(response)) {
					try {
						response.close();
					} catch (IOException e) {
						LOGGER.error(e.getLocalizedMessage(), e);
					}
				}
				if (Objects.nonNull(outputStream)) {
					try {
						outputStream.close();
					} catch (IOException e) {
						LOGGER.error(e.getLocalizedMessage(), e);
					}
				}
				if (Objects.nonNull(inputStream)) {
					try {
						inputStream.close();
					} catch (IOException e) {
						LOGGER.error(e.getLocalizedMessage(), e);
					}
				}
			}
		}
	}

	public static CookieStore getCookieStore() {
		return cookieStore;
	}

	public static void setCookieStore(CookieStore cookieStore) {
		HttpClientUtil.cookieStore = cookieStore;
	}
}
